package ba.jamax.ralfdb.controller;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.hibernate.criterion.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ModelAndView;

import ba.jamax.ralfdb.persistence.repository.generic.GenericRepository;
import ba.jamax.ralfdb.view.http.RequestGridViewModel;
import ba.jamax.ralfdb.view.http.ResponseGridViewModel;

public abstract class AbstractController<T> {

	@Autowired
	private ApplicationContext context;
	@Autowired
	private LocaleResolver localeResolver;

	private String mainView;

	private GenericRepository<T> repository;

	public void setMainView(String mainView) {
		this.mainView = mainView;
	}

	public String getMainView() {
		return mainView;
	}

	protected abstract Class<T> getPersistentClass();

	public GenericRepository<T> getRepository() {
		return repository;
	}

	public void setRepository(GenericRepository<T> repository) {
		this.repository = repository;
	}

	@RequestMapping(value = "/management", method = { RequestMethod.GET })
	public ModelAndView main(final HttpServletRequest request) {
		return new ModelAndView(getMainView());
	}

	@RequestMapping(value = "/management/index", method = { RequestMethod.POST })
	@Transactional(readOnly = true)
	@ResponseBody
	public ResponseGridViewModel<T> index(
			@RequestBody RequestGridViewModel gridViewModel,
			final HttpServletRequest request) {

		prepareGridViewModel(gridViewModel, request);

		return getFilteredEntity(gridViewModel);
	}

	@Transactional
	@RequestMapping(value = "/management/insert", method = { RequestMethod.POST })
	@ResponseBody
	public Map<String, ? extends Object> doInsert(@RequestBody final T t,
			final HttpServletRequest request) {

		Map<String, Object> responseMap = new HashMap<String, Object>();
		try {
			getRepository().addNew(t);
		} catch (Exception e) {
			String localizedMessage = this.context.getMessage(
					"General.list.message.error.add.failed", null,
					this.localeResolver.resolveLocale(request));
			responseMap.put("error", new String[] { localizedMessage });
		}
		return responseMap;
	}

	@Transactional
	@RequestMapping(value = "/management/update", method = { RequestMethod.POST })
	@ResponseBody
	public Map<String, ? extends Object> doUpdate(@RequestBody final T t,
			final HttpServletRequest request) {

		Map<String, Object> responseMap = new HashMap<String, Object>();
		try {
			getRepository().update(t);
		} catch (Exception e) {
			String localizedMessage = this.context.getMessage(
					"General.list.message.error.update.failed", null,
					this.localeResolver.resolveLocale(request));
			responseMap.put("error", new String[] { localizedMessage });
		}
		return responseMap;
	}

	@Transactional
	@RequestMapping(value = "/management/delete", method = { RequestMethod.POST })
	@ResponseBody
	public Map<String, ? extends Object> doDelete(
			@RequestParam("id") final Long entityId,
			final HttpServletRequest request) {

		Map<String, Object> responseMap = new HashMap<String, Object>();

		try {
			T t = getRepository().findById(entityId);
			if (t != null) {
				getRepository().delete(t);
			}
		} catch (Exception e) {
			String localizedMessage = this.context.getMessage(
					"General.list.message.error.delete.failed", null,
					this.localeResolver.resolveLocale(request));
			responseMap.put("error", new String[] { localizedMessage });
		}
		return responseMap;
	}

	@RequestMapping(value = { "/updatePreferedGridRowNum" }, method = { RequestMethod.POST })
	@ResponseBody
	public String updatePreferedGridRowNum(@RequestParam Integer newRowNum,
			HttpServletRequest request) {
		HttpSession session = request.getSession();
		session.setAttribute("gridRowNum", newRowNum);
		return null;
	}

	public ResponseGridViewModel<T> getFilteredEntity(
			RequestGridViewModel gridViewModel) {

		Map<String, Object> criteriaMap = gridViewModel.getCriteria();

		int page = gridViewModel.getPage();
		int rows = gridViewModel.getRows();

		// criterias
		if (gridViewModel.getSearch() && !gridViewModel.getCriteria().isEmpty()) {
			criteriaMap.putAll(gridViewModel.getCriteria());
		}
		/*
		 * if (!gridViewModel.getCriteria().containsKey("active")) {
		 * criteriaMap.put("active", Boolean.TRUE); }
		 */

		// ordering
		String sort = (StringUtils.isEmpty(gridViewModel.getSort())) ? "id"
				: gridViewModel.getSort();
		Order order = ("desc".equals(gridViewModel.getOrder())) ? Order
				.desc(sort) : Order.asc(sort);

		List<T> records = getRepository().findByCriteria(criteriaMap, false,
				(page - 1) * rows, rows, order);
		int totalRecords = getRepository().countByCriteria(criteriaMap, false);
		int totalPages = totalRecords / rows;
		if (totalRecords % rows > 0) {
			totalPages += 1;
		}
		ResponseGridViewModel<T> model = new ResponseGridViewModel<T>(records,
				page, totalPages, totalRecords);
		return model;
	}

	/**
	 * Process filtering criteria
	 * 
	 * @param gridViewModel
	 * @param request
	 */
	protected void prepareGridViewModel(
			final RequestGridViewModel gridViewModel,
			final HttpServletRequest request) {

		if (!gridViewModel.getCriteria().isEmpty()) {

			Map<String, Object> criteriaMap = new HashMap<String, Object>();

			for (String key : gridViewModel.getCriteria().keySet()) {
				Object value = gridViewModel.getCriteria().get(key);
				try {
					Method m = null;

					try {
						m = getPersistentClass()
								.getMethod(constructGetter(key));
					} catch (NoSuchMethodException e) {
						// it's not GET
						// let's try IS
						m = getPersistentClass().getMethod(
								constructIsGetter(key));
					}

					if (m.getReturnType().equals(value.getClass())) {
						criteriaMap.put(key, value);
					} else if (Integer.class.equals(m.getReturnType())) {
						criteriaMap.put(key, Integer.valueOf((String) value));
					} else if (Long.class.equals(m.getReturnType())) {
						criteriaMap.put(key, Long.valueOf((String) value));
					} else if (Boolean.class.equals(m.getReturnType())) {
						criteriaMap.put(key, Boolean.valueOf((String) value));
					}
				} catch (Exception e) {
					// skip method - out of the scope
				}
			}

			gridViewModel.setCriteria(criteriaMap);
		}
	}

	private String constructGetter(String strKey) {
		return "get" + WordUtils.capitalize(strKey);
	}

	private String constructIsGetter(String strKey) {
		return "is" + WordUtils.capitalize(strKey);
	}

	/**
	 * Generic entity validation on insert - could be overridden
	 * 
	 * @param entity
	 * @return
	 */
	protected Map<String, String> validateOnInsert(T entity) {
		return new HashMap<String, String>();
	}

	/**
	 * Generic entity validation on update - could be overridden
	 * 
	 * @param entity
	 * @return
	 */
	protected Map<String, String> validateOnUpdate(T entity) {
		return new HashMap<String, String>();
	}
}
